package com.vst.orm.search.jpa;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vst.orm.search.BaseSearchProcessor;
import com.vst.orm.search.Field;
import com.vst.orm.search.ISearch;
import com.vst.orm.search.SearchResult;
import com.vst.orm.util.InternalUtil;
import com.vst.orm.util.MetadataUtil;
import com.vst.orm.util.SearchUtil;

public class JPASearchProcessor extends BaseSearchProcessor {
	private static Logger logger = LoggerFactory.getLogger(JPASearchProcessor.class);

	public JPASearchProcessor(MetadataUtil mdu) {
		super(QLTYPE_EQL, mdu);
	}

	public List search(EntityManager entityManager, ISearch search) {
		if (search == null) {
			return null;
		}
		return search(entityManager, search.getSearchClass(), search);
	}

	public List search(EntityManager entityManager, Class<?> searchClass, ISearch search) {
		if ((searchClass == null) || (search == null)) {
			return null;
		}
		List<Object> paramList = new ArrayList();
		String ql = generateQL(searchClass, search, paramList);
		Query query = entityManager.createQuery(ql);
		addParams(query, paramList);
		addPaging(query, search);

		return transformResults(query.getResultList(), search);
	}

	public int count(EntityManager entityManager, ISearch search) {
		if (search == null) {
			return 0;
		}
		return count(entityManager, search.getSearchClass(), search);
	}

	public int count(EntityManager entityManager, Class<?> searchClass, ISearch search) {
		if ((searchClass == null) || (search == null)) {
			return 0;
		}
		List<Object> paramList = new ArrayList();
		String ql = generateRowCountQL(searchClass, search, paramList);
		if (ql == null) {
			return 1;
		}
		Query query = entityManager.createQuery(ql);
		addParams(query, paramList);

		return ((Number) query.getSingleResult()).intValue();
	}

	public SearchResult searchAndCount(EntityManager entityManager, ISearch search) {
		if (search == null) {
			return null;
		}
		return searchAndCount(entityManager, search.getSearchClass(), search);
	}

	public SearchResult searchAndCount(EntityManager entityManager, Class<?> searchClass, ISearch search) {
		if ((searchClass == null) || (search == null)) {
			return null;
		}
		SearchResult result = new SearchResult();
		result.setResult(search(entityManager, searchClass, search));
		if (search.getMaxResults() > 0) {
			result.setTotalCount(count(entityManager, searchClass, search));
		} else {
			result.setTotalCount(result.getResult().size() + SearchUtil.calcFirstResult(search));
		}
		return result;
	}

	public Object searchUnique(EntityManager entityManager, ISearch search) throws NonUniqueResultException {
		if (search == null) {
			return null;
		}
		return searchUnique(entityManager, search.getSearchClass(), search);
	}

	public Object searchUnique(EntityManager entityManager, Class<?> entityClass, ISearch search)
			throws NonUniqueResultException {
		if (search == null) {
			return null;
		}
		List<Object> paramList = new ArrayList();
		String ql = generateQL(entityClass, search, paramList);
		Query query = entityManager.createQuery(ql);
		addParams(query, paramList);
		try {
			return transformResult(query.getSingleResult(), search);
		} catch (NoResultException ex) {
		}
		return transformResult(null, search);
	}

	private void addParams(Query query, List<Object> params) {
		StringBuilder debug = null;

		int i = 1;
		for (Object o : params) {
			if (logger.isDebugEnabled()) {
				if (debug == null) {
					debug = new StringBuilder();
				} else {
					debug.append("\n\t");
				}
				debug.append("p");
				debug.append(i);
				debug.append(": ");
				debug.append(InternalUtil.paramDisplayString(o));
			}
			query.setParameter("p" + Integer.toString(i++), o);
		}
		if ((debug != null) && (debug.length() != 0)) {
			logger.debug(debug.toString());
		}
	}

	private void addPaging(Query query, ISearch search) {
		int firstResult = SearchUtil.calcFirstResult(search);
		if (firstResult > 0) {
			query.setFirstResult(firstResult);
		}
		if (search.getMaxResults() > 0) {
			query.setMaxResults(search.getMaxResults());
		}
	}

	private Object transformResult(Object result, ISearch search) {
		List results = new ArrayList(1);
		results.add(result);
		return transformResults(results, search).get(0);
	}

	private List transformResults(List results, ISearch search) {
		if (results.size() == 0) {
			return results;
		}
		int resultMode = search.getResultMode();
		if (resultMode == 0) {
			int count = 0;
			Iterator<Field> fieldItr = search.getFields().iterator();
			while (fieldItr.hasNext()) {
				Field field = (Field) fieldItr.next();
				if ((field.getKey() != null) && (!field.getKey().equals(""))) {
					resultMode = 3;
					break;
				}
				count++;
			}
			if (resultMode == 0) {
				if (count > 1) {
					resultMode = 1;
				} else {
					resultMode = 4;
				}
			}
		}
		switch (resultMode) {
		case 1:
			if (!(results.get(0) instanceof Object[])) {
				List<Object[]> rArray = new ArrayList(results.size());
				for (Object result : results) {
					rArray.add(new Object[] { result });
				}
				return rArray;
			}
			return results;
		case 2:
			List<List> rList = new ArrayList(results.size());
			/*
			if ((results.get(0) instanceof Object[])) {
				for (Object[] result : results) {
					List list = new ArrayList(result.length);
					for (Object o : result) {
						list.add(o);
					}
					rList.add(list);
				}
			} else {
				for (Object result : results) {
					List list = new ArrayList(1);
					list.add(result);
					rList.add(list);
				}
			}*/
			return rList;
		case 3:
			List<String> keyList = new ArrayList();
			Iterator<Field> fieldItr = search.getFields().iterator();
			while (fieldItr.hasNext()) {
				Field field = (Field) fieldItr.next();
				if ((field.getKey() != null) && (!field.getKey().equals(""))) {
					keyList.add(field.getKey());
				} else {
					keyList.add(field.getProperty());
				}
			}
			List<Map<String, Object>> rMap = new ArrayList(results.size());
			/*
			if ((results.get(0) instanceof Object[])) {
				for (Object[] result : results) {
					Map<String, Object> map = new HashMap();
					for (int i = 0; i < keyList.size(); i++) {
						String key = (String) keyList.get(i);
						if (key != null) {
							map.put(key, result[i]);
						}
					}
					rMap.add(map);
				}
			} else if (keyList.size() == 1) {
				for (Object result : results) {
					Map<String, Object> map = new HashMap();
					if (keyList.get(0) != null) {
						map.put(keyList.get(0), result);
					}
					rMap.add(map);
				}
			} else {
				throw new RuntimeException(
						"Unexpected condition: a single object was returned from the query for each record, but the Search expects multiple.");
			}*/
			return rMap;
		}
		return results;
	}
}
